/** 
Copyright (c) 2010, Sheikh Abdul Wahid Ahmed
Details @ http://extremecodeworld.codeplex.com/license
**/

using System;
using System.Collections;
using System.Data;
using EXtremecode.DataAccessLayer;
using EXtremecode.Utils;
using EXtremecode.Common;
using DAConfig=EXtremecode.DataAccessLayer.Configurations; 
using System.Collections.Generic;
using System.Text;


namespace EXtremecode.BusinessLayer
{
    
    
    /// <summary>
	/// Summary description for BusinessObject.
	/// </summary>
	public  abstract class BusinessObject
	{
		
		private DataAccessor  m_da; //shared by multiple objects
		private bool isEditMode=false;
		protected BusinessObjectPaper m_paper; //data container
        protected string m_objectTypeKey = string.Empty;
        internal static string OBJECT_TYPE_KEY_FORMAT_STRING = "DA:{0}_TBL:{1}"; 
        //command creater delegates
        protected CommandCreatorHandler selectCmdCreator,updateCmdCreator,deleteCmdCreator,insertCmdCreator;
        
        
        //command pools
        private static Dictionary<string, ObjectPool> m_daUpdateCommandPools = new Dictionary<string, ObjectPool>();
        private static Dictionary<string, ObjectPool> m_daInsertCommandPools = new Dictionary<string, ObjectPool>();
        private static Dictionary<string, ObjectPool> m_daDeleteCommandPools = new Dictionary<string, ObjectPool>();
        
        //asking user to take decision [event]
        public static event TakeDecisionDelegate<string, DataRow, bool> OnAskingToUpdateDeletedRow;    
        
        
        //for new Object
		protected BusinessObject():this(DataAccessorCollection.DefaultAccessorName){}
		protected BusinessObject(string strConnectionId)
		{
			try
			{
				
				PopulateWithCommandCreators(ref selectCmdCreator,ref updateCmdCreator,ref insertCmdCreator,ref deleteCmdCreator);
				m_da=DataAccessorCollection.Instance[strConnectionId];
				m_paper=new BusinessObjectPaper(GetFieldInfoCollection());
                m_objectTypeKey = string.Format(OBJECT_TYPE_KEY_FORMAT_STRING, strConnectionId, Table_Name);
			}
			catch(Exception ex)
			{
				throw Logging.Logger.Write(ex);
			}
			finally
			{
				//Logging.Logger.Write();
			}

		}


       
       
        //for current object
        protected BusinessObject(BusinessObjectPaper boPaper):
            this(boPaper, DataAccessorCollection.Instance[DataAccessorCollection.DefaultAccessorName])
        {}

        protected BusinessObject(BusinessObjectPaper boPaper, string strConnectionId) :
            this(boPaper, DataAccessorCollection.Instance[strConnectionId])
        { }

        protected BusinessObject(BusinessObjectPaper boPaper, DataAccessor da)
        {
            PopulateWithCommandCreators(ref selectCmdCreator, ref updateCmdCreator, ref insertCmdCreator, ref deleteCmdCreator);
            m_da = da;
            m_paper = boPaper;
            isEditMode = true;
            m_objectTypeKey = string.Format(OBJECT_TYPE_KEY_FORMAT_STRING, da.Name, Table_Name);

        }

        public BusinessObjectPaper Paper
        {
            get
            {
                return m_paper;
            }
        }
        
        private DACommand GetInsertCommand()
        {
            
                //create pool if not created.
                lock (m_daInsertCommandPools)
                {
                    if (!m_daInsertCommandPools.ContainsKey(m_objectTypeKey))
                    {
                        ObjectPool pool =
                            new ObjectPool(m_objectTypeKey + " (Insert Command)", (CreateObjectDelegate<IPooledObject>)delegate
                            {
                                return m_da.Create_InsertDACommand(Table_Name, View_Name, m_paper.FieldsDefinitions, insertCmdCreator);
                            });

                        m_daInsertCommandPools.Add(m_objectTypeKey, pool);

                    }
                }
                return (DACommand)m_daInsertCommandPools[m_objectTypeKey].GetObject();
            
        }

        private DACommand GetUpdateCommand()
        {
            
                
                //create pool if not created.
                lock (m_daUpdateCommandPools)
                {
                    if (!m_daUpdateCommandPools.ContainsKey(m_objectTypeKey))
                    {
                        ObjectPool pool =
                            new ObjectPool(m_objectTypeKey + " (Update Command)", (CreateObjectDelegate<IPooledObject>)delegate
                            {
                                return m_da.Create_UpdateDACommand(Table_Name, View_Name, m_paper.FieldsDefinitions, updateCmdCreator);
                            });

                        m_daUpdateCommandPools.Add(m_objectTypeKey, pool);

                    }
                }
                return (DACommand)m_daUpdateCommandPools[m_objectTypeKey].GetObject();
            
        }

        private DACommand GetDeleteCommand()
        {

            //create pool if not created.
            lock (m_daDeleteCommandPools)
            {
                if (!m_daDeleteCommandPools.ContainsKey(m_objectTypeKey))
                {
                    ObjectPool pool =
                        new ObjectPool(m_objectTypeKey + " (Delete Command)", (CreateObjectDelegate<IPooledObject>)delegate
                        {
                            return m_da.Create_DeleteDACommand(Table_Name, m_paper.FieldsDefinitions, deleteCmdCreator);
                        });

                    m_daDeleteCommandPools.Add(m_objectTypeKey, pool);

                }
            }
            return (DACommand)m_daDeleteCommandPools[m_objectTypeKey].GetObject();

        }



        private void CleanDirtyParams(DACommand daCommand)
        {

            //modified fields.
            foreach (string key in m_paper.ModifiedKeys)
            {
                daCommand.CleanParam(key);
            }

            //primary key
            foreach (FieldInfo field in m_paper.FieldsDefinitions.KeyFields)
            {
                daCommand.CleanParam(field.Name + "_" + Constants.ParamCustomType.CONDITIONAL);
            }


            //for special fields like [time stamp]
            foreach (FieldInfo field in m_paper.FieldsDefinitions.SpecialFields)
            {
                daCommand.CleanParam(field.Name + "_" + Constants.ParamCustomType.CONDITIONAL);
            }



        }

        public void PopulateThis(DataRow dr)
        {
            PopulatePaper(dr, m_paper);
        }
        public static void PopulatePaper(DataRow dr,BusinessObjectPaper paper)
        {
            foreach (DataColumn col in dr.Table.Columns)
            {
                if (dr[col.ColumnName, DataRowVersion.Current] != dr[col.ColumnName, DataRowVersion.Original])
                {
                    //field value changed
                    paper[col.ColumnName] = dr[col.ColumnName, DataRowVersion.Current];
                }
                paper[col.ColumnName, true] = dr[col.ColumnName,DataRowVersion.Original];
                
            }
        }
        private void PaperToCommand(DACommand daCommand)
        {
            
            //updated data
            foreach (string key in  m_paper.ModifiedKeys)
            {
                daCommand[key] = m_paper[key];
            }

            //primary key
            foreach (FieldInfo field in m_paper.FieldsDefinitions.KeyFields)
            {
                //first check original value if it fout null then use current one.
                daCommand[field.Name + "_" + Constants.ParamCustomType.CONDITIONAL] = 
                    (m_paper[field.Name,true]!=null)
                    ? m_paper[field.Name,true]
                    : m_paper[field.Name];
            }
            //for special fields like [time stamp]
            foreach (FieldInfo field in m_paper.FieldsDefinitions.SpecialFields)
            {
                //first check original value if it fout null then use current one.
                daCommand[field.Name + "_" + Constants.ParamCustomType.CONDITIONAL] =
                    (m_paper[field.Name, true] != null)
                    ? m_paper[field.Name, true]
                    : m_paper[field.Name];
            }
        }
        public void Update()
        {
            isEditMode = true;
            Save(false); 
        }

        public void Update(bool isTransationBased)
        {
            isEditMode = true;
            Save(isTransationBased); 
        }


        public void Insert()
        {
            isEditMode = false;
            Save(false);
        }

        public void Insert(bool isTransationBased)
        {
            isEditMode = false;
            Save(isTransationBased);
        }


        public void Save()
		{
            Save(false);
		}
		public void Save(bool isTransationBased)
		{

            //excluding conditions
            if (!DataEncryptor.IsAllowed)
            {
                Logging.Logger.Write("Unregistered");
                return;
            } 
            else if (!m_paper.IsModified)
            {
                return;
            }


            try
            {


                //For whenDeleted field
                if (!doAppropriateWrokWhenEntityContains_WhenDeleted_Field(isTransationBased))
                {
                    return;
                }

                #region Insert
                if (!isEditMode)
                {
                    //get command, execute reader, and release commad to pool.
                    DACommand daCommand = GetInsertCommand();
                    DADataReader rdr = null;
                    try
                    {
                        PaperToCommand(daCommand);
                        try
                        {
                            if (isTransationBased)
                            {
                                rdr = daCommand.ExecuteReaderForTransaction();
                            }
                            else
                            {
                                rdr = daCommand.ExecuteReader();
                            }
                        }
                        finally
                        {
                            CleanDirtyParams(daCommand);
                        }

                    }
                    finally
                    {
                        daCommand.Pool.ReleaseObject(daCommand);
                    }



                    //refresh cache and read data from reader.
                    try
                    {
                        //refresh cache item
                        RefreshCache();


                        //read data from reader.
                        if (rdr.MoveNext())
                        {
                            ReaderToPaper(m_paper, rdr);
                            isEditMode = true;
                        }
                    }
                    finally
                    {
                        rdr.Close();
                    }
                }
                #endregion Insert

                #region Update
                else
                {
                    //get command, execute reader, and release commad to pool.
                    DACommand daCommand = GetUpdateCommand();
                    DADataReader rdr = null;
                    try
                    {
                        PaperToCommand(daCommand);
                        try
                        {
                            if (isTransationBased)
                            {
                                rdr = daCommand.ExecuteReaderForTransaction();
                            }
                            else
                            {
                                rdr = daCommand.ExecuteReader();
                            }
                        }
                        finally
                        {
                            CleanDirtyParams(daCommand);
                        }
                    }
                    finally
                    {
                        daCommand.Pool.ReleaseObject(daCommand);
                    }


                    //refresh cache and read data from reader.
                    try
                    {
                        if (rdr.RecordsAffected == 0)
                        {
                            throw new DBConcurrencyException("[UC] Row is not updated due to conflict of data");
                        }
                        else
                        {
                            //refresh cache item
                            RefreshCache();


                            //read reader data.
                            if (rdr.MoveNext())
                            {
                                ReaderToPaper(m_paper, rdr);
                            }
                        }

                    }
                    finally
                    {
                        rdr.Close();
                    }


                }
                #endregion Updated


                //mark changes to original
                m_paper.Accept();




            }
            catch (Exception ex)
            {
                throw Logging.Logger.Write(ex);
            }
			finally
			{
				Logging.Logger.Write();
			}

			
		}
        private bool doAppropriateWrokWhenEntityContains_WhenDeleted_Field(bool transactionBased)
        {
            if (!isEditMode && !string.IsNullOrEmpty(DAConfig.Provider.WhenDeletedColumnName)
                    && m_paper.FieldsDefinitions.Contains(DAConfig.Provider.WhenDeletedColumnName))
            {
                //1- check row exist in DB with user's mentioned data.
                string whereClause = string.Empty;
                string strAnd = string.Empty;

                foreach (string fieldName in FiledsForCheckingDuplication)
                {
                    if (this[fieldName] == null)
                    {
                        throw new Exception(@"[When Deleted] value of fields selected for duplication check, should not be null.");
                    }

                    whereClause += strAnd + string.Format("{0} = '{1}'",
                        fieldName, this[fieldName].ToString());
                    strAnd = " And ";
                }

                if (whereClause == string.Empty)
                {
                    throw new Exception(@"[When Deleted] no field is selected for duplication check.");
                }

                DataTable dt = m_da.doExecuteFillQuery(string.Format("select * from {0} where {1} and {2} is not null",
                        View_Name, whereClause, DAConfig.Provider.WhenDeletedColumnName)
                     , transactionBased);

                if (dt.Rows.Count == 0)
                {
                    //let the system create new row.
                    return true;

                }
                else
                {
                    //deleted row found.
                    DataRow dr = dt.Rows[0];

                    //ask user to update deleted row
                    if (OnAskingToUpdateDeletedRow != null)
                    {
                        if (!OnAskingToUpdateDeletedRow(Table_Name, dr))
                        {
                            //user want to cancel this updation
                            return false;
                        }
                    }

                    //Incorporate updates in DataRow
                    foreach (string fieldName in m_paper.ModifiedKeys)
                    {
                        dr[fieldName] = (m_paper[fieldName] == null) ?
                            DBNull.Value :
                            m_paper[fieldName];
                    }

                    //get this object ready for update [not insertion]
                    PopulateThis(dr);
                    isEditMode = true;

                    this[DAConfig.Provider.WhenDeletedColumnName] = null;//[considering not deleted row]
                    return true;
                }


            }
            else
            {
                return true; 
            }
        }
        protected virtual IEnumerable<string> FiledsForCheckingDuplication
        {
            get
            {
                foreach (FieldInfo fieldInfo in m_paper.FieldsDefinitions.KeyFields)
                {
                    yield return fieldInfo.Name; 
                }
            }
        }
		public void Delete()
		{
            Delete(false);
		}
        public void Delete(bool isTransationBased)
        {

            try
            {
                //get command, execute, and release commad to pool.
                DACommand daCommand = GetDeleteCommand();
                DADataReader rdr = null;
                try
                {
                    PaperToCommand(daCommand);
                    try
                    {
                        if (isTransationBased)
                        {
                            daCommand.ExecuteNoneQueryForTransaction();
                        }
                        else
                        {
                            daCommand.ExecuteNoneQuery();
                        }
                    }
                    finally
                    {
                        CleanDirtyParams(daCommand);
                    }

                }
                finally
                {
                    daCommand.Pool.ReleaseObject(daCommand);
                }


                //refresh cache item
                RefreshCache();
            }
            catch (Exception ex)
            {
                throw Logging.Logger.Write(ex);
            }
            finally
            {
                //Logging.Logger.Write();
            }



        }
		internal static void ReaderToPaper(BusinessObjectPaper paper,DADataReader rdr)
		{
			try
			{
				foreach(string  key in paper.FieldsDefinitions.Keys)
				{
					paper[key] = rdr[key];
				}
                paper.Accept();
			}
			catch(Exception ex)
			{
				throw Logging.Logger.Write(ex);
			}
			finally
			{
				//Logging.Logger.Write();
			}


		}
		public virtual object this[string fieldName]
		{
			get
			{
				return m_paper[fieldName];
			}
			set
			{
				m_paper[fieldName]=value;
			}
		}

		
        
        protected abstract FieldInfoCollection  GetFieldInfoCollection();
		protected abstract string Table_Name{get;}
		protected abstract string View_Name{get;}
		protected abstract string DataTable_Name{get;}

		protected abstract BusinessObjectRelationCollection ChildRelations
		{get;}
		protected abstract BusinessObjectRelationCollection ParentRelations
		{get;}
		protected abstract void PopulateWithCommandCreators(ref CommandCreatorHandler selectCmdCreator,ref CommandCreatorHandler updateCmdCreator,ref CommandCreatorHandler insertCmdCreator,ref CommandCreatorHandler deleteCmdCreator);
		protected abstract void RefreshCache();

        public override string ToString()
		{

            return m_paper.ToString();
            
		}
        public string ObjectData
        {
            get
            {
                return ToString();
            }
        }




	}
}
